梦入琼楼寒有月,行过石树冻无烟

Node.js Mocha

Node.js 是一个面向服务端的 JavaScript,为了能够有效的持续保证代码质量,通过单元测试(Unit Testing) 来进行正确性校验的测试工作,Node.js 也有 MochaShould 以及 SuperTest 模块用于。

单元测试主要的目的就是这对程序模块中进行正确性检验测试工作,而程序单元是应用的最小测试部件。

一个单元就是单个程序或者函数、过程等,而对于面向对象编程,则最小单元就是方法和超类、子类中的方法。

在此之前,JavaScript 主要用于在前端,自从 node.js 起始以来 JavaScript 开始面向客户端以及跨平台的使用,这时单元测试就显得尤为重要。we

BDD or TDD

TDD

单元测试主要分为 BDD(Behavior-Driven Development, 行为驱动开发),以及TDD(Test-Driven Development,测试驱动开发)

其中 TDD 原理是指在开发功能之前先编写单元测试的用例代码,TDD 也是 极限编程(Extreme programming,xp)的实现.

极限编程是软件工程方法学中敏捷软件开发的一种,他更倾向与适应性以及可预测性,即在任何阶段去适应变化。

BDD

行为驱动开发(Behavior-driven development,BDD)是敏捷开发技术之一,他也是极限开发(Extreme Programming)鼓励软件项目的开发者和非技术人员和以及商业参与者之间的协作并保证最后的 QA(Quality Assurance)

这主要是出于 2009 年 Dan North 对 BDD 给出的定义:

BDD是第二代的、由外及内的、基于拉(pull)的、多方利益相关者的(stakeholder)、多种可扩展的、高自动化的敏捷方法。它描述了一个交互循环,可以具有带有良好定义的输出(即工作中交付的结果):已测试过的软件。

Mocha

Mocha 主要是用于 JavaScript 的测试框架,使得异步测试变得简单且 “有趣”。他最为重要的是 Mocha 测试是连续的,在正确的测试条件中遇到未捕获的异常时将会给出灵活且准确的报告。

Assert

断言(Assertion)是单元测试中用来保证最小单元是否正常的测试方法之一,目的是验证开发者所预期的效果,当程序运行到断言位置时会返回布尔值,其中返回为真则表示正常,否则将会终止运行并给出错误消息。

是 Mocha 中的核心方法之一,这由 Node.js 官方库所进行提供,并在 Node.js 的 API 中定义了多种检测方法供开发者使用,将方法与 Mocha 进行配合来以低成本的方式进行单元测试。

Id Name Info
1 assert(value,[message]) 当 value 为 false 时终止运行并返回错误
assert.ok(value,[message]) assert 的别名
2 assert.ifError(value) 如果 value 不是 nullundefined 则报错并抛出 value
3 assert.match(value,regExp) value 是否匹配正则表达式
4 assert.notEqual(actual,expected[,message]) 严格匹配 actual != expected 则正常运行,否则抛出异常错误
assert.notStrictEqual(actual,expected[,message]) assert.notEqual 的别名
assert.notDeepStricEqual(actual,expect[,message]) 深度匹配 actual != expected 是否相等,assert.notEqual 的别名
5 assert.equal(actual,expected[,message]) 严格匹配 actual 与 expected 是否相等
assert.strictEqual(actual,expected[,message]) Euqal 别名
assert.deepEqual(actual,expected[,message]) Equal 别名
assert.deepStrictEqual(actual,expected[,message]) Equal 别名
6 assert.throws(function[,error][,message]) 判断代码快是否抛出异常并自定义异常错误
assert.doesNotThrow(function[,error][,message]) 捕获错误然后重新抛出错误

assert or assert.ok

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const assert = require('assert')

/*
AssertionError [ERR_ASSERTION]: 当 assert 为 false 时终止运行并返回错误
at Object.<anonymous> (/home/kunlun/Development/Web/Demo/node/test.js:3:8)
at Module._compile (internal/modules/cjs/loader.js:1072:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
at Module.load (internal/modules/cjs/loader.js:937:32)
at Function.Module._load (internal/modules/cjs/loader.js:778:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47 {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '=='
}
*/

//assert.ok(false,'当 assert 为 false 时终止运行并返回错误') or
assert(false,'当 assert 为 false 时终止运行并返回错误')

assert.ifError()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const assert = require('assert')

assert.ifError(null)

/*
AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'Error'
at Object.<anonymous> (/home/kunlun/Development/Web/Demo/node/test.js:4:8)
at Module._compile (internal/modules/cjs/loader.js:1072:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
at Module.load (internal/modules/cjs/loader.js:937:32)
at Function.Module._load (internal/modules/cjs/loader.js:778:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47 {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'Error',
expected: null,
operator: 'ifError'
}
*/
assert.ifError('Error')

assert.match

1
2
3
4
5
6
7
8
9
10
const assert = require('assert')

// ok
assert.match('hey,assert!', /[a-z]/);

/*
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /[a-z]/. Input:
'2021'
*/
assert.match('2021', /[a-z]/)

assert.notEqual or assert.notStricteEqueal at assert.notDeepStrictEuqal

1
2
3
4
5
6
7
8
9
10
const assert = require('assert')

// ok
assert.notEqual(1,2,'ok')

// AssertionError [ERR_ASSERTION]: This is !=
assert.notStrictEqual(1,1,'This is !=')

// ok
assert.notDeepStrictEqual(1,'1','This is ok!')

assert.equal at assert.strictEqual() or deepEqual() and deepStrictEqual()

1
2
3
4
5
6
7
8
9
10
11
// ok
assert.equal(1,1,'This is ok')

// ok
assert.strictEqual(1,1,'This is ok')

// ok
assert.deepEqual(1,1,'This is ok')

// AssertionError [ERR_ASSERTION]: This is error
assert.deepStrictEqual(1,2,'This is error')

asset.throws()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const assert = require('assert')

/*
AssertionError [ERR_ASSERTION]: Throw an exception error
at Object.<anonymous> (/home/kunlun/Development/Web/Demo/node/test.js:4:8)
at Module._compile (internal/modules/cjs/loader.js:1072:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
at Module.load (internal/modules/cjs/loader.js:937:32)
at Function.Module._load (internal/modules/cjs/loader.js:778:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47 {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: Error: Error value
at assert.throws.code (/home/kunlun/Development/Web/Demo/node/test.js:6:15)
at getActual (assert.js:765:5)
at Function.throws (assert.js:911:24)
at Object.<anonymous> (/home/kunlun/Development/Web/Demo/node/test.js:4:8)
at Module._compile (internal/modules/cjs/loader.js:1072:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
at Module.load (internal/modules/cjs/loader.js:937:32)
at Function.Module._load (internal/modules/cjs/loader.js:778:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47,
expected: {
code: 404,
name: 'TypeError',
message: 'Error value',
info: { nested: true, data: 'nested ok' }
},
operator: 'throws'
}
*/
assert.throws(
() => {
throw new Error('Error value')
},{
code: 404,
name: 'TypeError',
message: 'Error value',
info: {
nested: true,
data: "nested ok"
}
},'Throw an exception error'
)

assert.doesNotThrow()

1
2
3
4
5
6
7
8
const assert = require('assert')

// AssertionError [ERR_ASSERTION]: Got unwanted exception: This will print erroras
assert.doesNotThrow(
() => {
throw new TypeError('Error value')
},'This will print error'
)

demo

app.js

首先我们需要通过一个计算的函数来进行添加 tests.js 测试用例,并最终通过测试用例进行检测代码是否通过测试:

1
2
3
4
5
6
7
8
exports.add = function (i,j) {
return i + j
}

exports.mul = function (i,j) {
return i * j
}

appTests.js

在 Mocha 中,describe 方法的主要作用就是添加一段描述而 it 则是向测试回调方法中添加一个参数即 done,Mocha 将会等待调用此函数以完成测试,并接受 Error 实例。

需要注意的 it() 任何东西都是无效使用并引发错误,这通常会导致测试失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const assert = require('assert')
const calc = require('./app')

/*
$ npm test
> node@1.0.0 test /home/kunlun/Development/Web/Demo/node
> mocha work.js

Calculator Tests
Addition Tests
✔ returns 1 + 1 = 2
✔ should returns 1 + -1 = 0
✔ returns 0 * 4 = 4
Multiplication Tests
✔ returns 2 * 2 = 4
*/
describe('Calculator Tests', function () {
describe('Addition Tests', function () {
it('returns 1 + 1 = 2', function (done) {
assert.equal(calc.add(1,1),2)
done();
})

it('should returns 1 + -1 = 0', function (done) {
assert.equal(calc.add(1,-1),0)
done()
});

describe('Multiplication Tests', function () {
it('returns 2 * 2 = 4', function (done) {
assert.equal(calc.mul(2,2),4)
done()
});
});

it('returns 0 * 4 = 4', function (done) {
assert.equal(calc.mul(2,2),4)
done()
});
});
});

在上述 code 中,我们使用了 node.js 那种的 assert 模块,通常如果抛出一个 Error 就被执行工作,并向 app.js 添加 ij 参数值。

istanbul

istanbul 是一个使用行计数器检测 ES5、ES6 的 JavaScript 代码,来跟踪单元测试和代码的执行情况(也就是单元测试对代码的覆盖率),可直接根据 mocha 来添加 istanbul:

1
npm install --save-dev nyc

之后在 package.json 中将 nyc 放入 test 之前即可:

1
2
3
"scripts": {
"test": "nyc mocha work.js"
},

通过 npm test 运行后可以看到 instanbul 所提供的单元测试覆盖率检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  Calculator Tests
Addition Tests
✔ returns 1 + 1 = 2
✔ should returns 1 + -1 = 0
✔ returns 0 * 4 = 4
Multiplication Tests
✔ returns 2 * 2 = 4


4 passing (5ms)

----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
app.js | 100 | 100 | 100 | 100 |
work.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
⬅️ Go back